import { filterData, setLoadingStatus, standardizedUrl } from "@/http/tools";
import { PlainObjType } from "@/typings";
import axios, { AxiosHeaders, AxiosRequestConfig, AxiosResponse, CancelTokenSource } from "axios";
import qs from "qs";

// http 请求页面交互
import httpInteracte from "./interacte";

/**
 * axios source 类型
 */
type SourceType = CancelTokenSource & {
  url: string;
  method: string;
  data: object;
  exp: number;
};

// 请求队列（axios.CancelToken.source）数组
export let sourceQueue: SourceType[] = [];

class Http {
  /**
   * 可以在 Http 类外部修改
   */
  static maxRetryTimes: number = 3; // 请求失败最大重试次数
  static defaultTimeout = 1000 * 6; //  响应超时时限
  static RESPONSE_OK_CODE: number = 0; // 请求正常响应时的接口返回 code 值，默认 0

  /**
   * 定义 Http 类实例属性类型
   * 设置实例初始化时的其他参数 this.xxx，在 Http 类外部修改不能修改
   */
  url: string;
  data: object;
  config: PlainObjType;
  currentRetryCount: number;
  method: string;
  timeout: number;
  source: SourceType | null;
  defaultBaseURL: string;

  /**
   * new Http 类时需要的参数
   * @param url 请求路径
   * @param data 请求参数
   * @param config 请求额外配置
   */
  constructor(url: string = "", data: PlainObjType = {}, config: AxiosRequestConfig = {}) {
    // 路径
    this.url = url;

    // 请求参数
    this.data = filterData(data);

    // headers 里面的 Content-Type 默认设置为 json
    this.config = {
      headers: { "Content-Type": "json" },
      ...config,
    };

    // 记录当前实例重试次数，默认 0 次
    this.currentRetryCount = 0;

    // 默认请求方式
    this.method = "post";

    // 默认请求超时时间
    this.timeout = config?.timeout || Http.defaultTimeout; // 响应超时时限

    // 取消请求 source
    this.source = null;

    // 请求接口时，添加请求前缀
    this.defaultBaseURL = "/api";
  }

  /**
   * 默认处理 params 方法
   * 被这些类 PostHttp、PutHttp、GetHttp、DeleteHttp 继承时，可以覆盖
   * @returns
   */
  convertParams() {
    return {};
  }

  /**
   * 默认处理 data 方法
   * 被这些类 PostHttp、PutHttp、GetHttp、DeleteHttp 继承时，可以覆盖
   * @returns
   */
  convertData() {
    return {};
  }

  /**
   * 默认处理 headers 方法
   * 被这些类 PostHttp、PutHttp、GetHttp、DeleteHttp 继承时，可以覆盖
   * @returns
   */
  convertHeaders: () => any = async () => {
    const headers: AxiosHeaders | PlainObjType = {};

    switch (this.config.headers["Content-Type"]) {
      case "json":
        headers["Content-Type"] = "application/json;charset=utf-8";
        break;
      case "formType":
        headers["Content-Type"] = "application/x-www-form-urlencoded;charset=utf-8";
        break;
      case "formData":
        headers["Content-Type"] = "multipart/form-data;charset=utf-8";
        break;
      default:
        headers["Content-Type"] = "application/json;charset=utf-8";
        break;
    }

    // 设置请求头 authorization
    httpInteracte["SET_HEADER_AUTH"](headers);

    return headers;
  };

  /**
   * 默认处理 url 方法
   * 被这些类 PostHttp、PutHttp、GetHttp、DeleteHttp 继承时，可以覆盖
   * @returns
   */
  convertUrl: () => string = () => {
    const flag = import.meta.env.VITE_APP_API_FLAG || "";

    // 获取实际的 baseURL
    const trueBaseURL: string = this.config.baseURL
      ? standardizedUrl(this.config.baseURL)
      : standardizedUrl(this.defaultBaseURL);

    // 返回最终的请求路径
    return `${flag}${trueBaseURL}${standardizedUrl(this.url)}`;
  };

  /**
   * 没退出 while 循环时不弹出错误信息
   */
  isOutLoop() {
    return this.currentRetryCount >= Http.maxRetryTimes;
  }

  /**
   * 封装请求
   */
  async _request() {
    try {
      /**
       * 创建接口取消 source
       * 并添加到请求队列 sourceQueue 中
       */
      this.source = {
        ...axios.CancelToken.source(),
        url: this.url,
        method: this.method,
        data: this.data,
        exp: Date.now(),
      };

      // 添加当前请求到请求队列中
      sourceQueue.push(this.source);

      // 开启 loading
      setLoadingStatus("show", this.config);

      // 发起请求
      const res: AxiosResponse = await axios({
        method: this.method,
        url: this.convertUrl(),
        params: this.convertParams(),
        data: this.convertData(),
        headers: await this.convertHeaders(),
        timeout: this.timeout,
        cancelToken: this.source.token,
        responseType: this.config.responseType || "json",
      });

      console.log(`↓↓↓↓↓↓ ${this.convertUrl()} ↓↓↓↓↓↓`);
      console.log(res.data);
      console.log(`↑↑↑↑↑↑ ${this.convertUrl()} ↑↑↑↑↑↑`);

      // 返回正常响应结果
      return res;
    } catch (error: unknown) {
      /**
       * 请求报错时，返回 Promise.reject
       * 供后续对错误 error 的处理
       */
      return Promise.reject(error);
    } finally {
      // 清空当前接口取消 source
      this.source = null;
      // 本次请求完成后将 this.source 从请求队列 sourceQueue 中删掉
      sourceQueue = sourceQueue.filter(item => item !== this.source);
      // 取消 loading
      setLoadingStatus("close");
    }
  }

  /**
   * 开始请求
   * while 循环处理接口重试逻辑（目前只有请求超时时，才会重试接口）
   * 请求正常时，return 会断开 while 循环，函数返回正常响应的接口 return this.responseSuccess(res);
   * 请求异常且没有匹配到异常情况时，函数返回 undefined
   * @returns
   */
  async request() {
    while (++this.currentRetryCount <= Http.maxRetryTimes) {
      try {
        // 调用封装好的请求
        const res: AxiosResponse = await this._request();

        /**
         * 请求未出错时，返回响应结果
         * return 退出 while 循环
         */
        return this.responseSuccess(res);
      } catch (error: any) {
        /**
         * 请求出错时，判断错误类型，执行对应逻辑
         */
        return this.responseError(error);
      }
    }
  }

  /**
   * 接口请求正常时，拦截数据，做相关处理
   * @param res
   * @returns
   */
  responseSuccess(res: AxiosResponse) {
    /**
     * 请求成功
     * 接口返回 res.data.code 不是正常 code 时报错处理，提示 res.data.msg || 接口报错
     */
    if (![Http.RESPONSE_OK_CODE].includes(res.data.code) && this.config.responseType !== "blob") {
      httpInteracte["API_ERROR"](res);
      return Promise.reject(res.data.msg);
    }

    /**
     * 接口返回 res.data.code 是正常 code 时，判断是否为下载文件
     * 文件返回整个 res，不是文件返回 res.data
     */
    return this.config.responseType === "blob" ? res : res.data;
  }

  /**
   * 接口请求失败时，拦截数据，做相关处理
   * @param res
   * @returns
   */
  async responseError(error: any) {
    /**
     * 是否请求超时判定
     * 需要判重复调用接口，不处理 this.currentRetryCount while 循环中自行累加）
     */
    const isTimeoutError: boolean =
      error.code === "ECONNABORTED" && error.message && error.message.includes("timeout");

    /**
     * 是否请求取消判定
     * 默认取消时 error.message 返回 canceled
     * 这里取对我们自定义的取消 error.message = cancel-by-self 做拦截，调用时 xxx.cancel("cancel-by-self")
     * 自定义取消时会跳出 while 循环
     * 默认取消时只会取消当次请求，不会跳出 while 循环
     */
    const isCancelError: boolean =
      error.code === "ERR_CANCELED" && error.message && error.message.includes("cancel-by-self");

    /**
     * 是否为请求节流报错判定
     * 不重复调用接口，将 this.currentRetryCount 置为 Http.maxRetryTimes, 退出 while 循环
     */
    const isRequestThrottleError: boolean = error.message === "request throttle";

    /**
     * 是否为防抖请求报错判定 request debounce
     * 不重复调用接口，将 this.currentRetryCount 置为 Http.maxRetryTimes, 退出 while 循环
     */
    const isRequestDebounceError: boolean = error.message === "request debounce";

    /**
     * 自定义 http 状态码报错
     */
    const isStatusError: boolean = error.response;

    if (isTimeoutError) {
      /**
       * case 1 请求超时
       * 响应超时处理
       * 需要判重复调用接口，不处理 this.currentRetryCount while 循环中自行累加）
       */
      if (this.isOutLoop()) {
        httpInteracte["API_TIMEOUT"]();
      }

      // 最后一次时返回 Promise.reject, 退出 while 循环
      if (this.currentRetryCount === Http.maxRetryTimes) {
        return Promise.reject(error.message);
      }
    } else if (isCancelError) {
      /**
       * case 2 取消请求
       * 自行取消接口
       * 不重复调用接口，将 this.currentRetryCount 置为 Http.maxRetryTimes, 退出 while 循环
       */
      this.currentRetryCount = Http.maxRetryTimes;

      return Promise.reject(error.message);
    } else if (isRequestThrottleError) {
      /**
       * case 3 请求节流报错
       * 不重复调用接口，将 this.currentRetryCount 置为 Http.maxRetryTimes, 退出 while 循环
       */
      this.currentRetryCount = Http.maxRetryTimes;
      return Promise.reject(error.message);
    } else if (isRequestDebounceError) {
      /**
       * case 4 取消请求
       * 自行取消接口
       * 不重复调用接口，将 this.currentRetryCount 置为 Http.maxRetryTimes, 退出 while 循环
       */
      this.currentRetryCount = Http.maxRetryTimes;

      return Promise.reject(error.message);
    } else if (isStatusError) {
      /**
       * case 5 自定义 http 状态码报错
       * http状态码错误处理
       * 不重复调用接口，将 this.currentRetryCount 置为 Http.maxTryTimes, 退出 while 循环
       */
      this.currentRetryCount = Http.maxRetryTimes;

      // 当前请求的响应 http 状态码
      const status: number = error.response.status;

      if ([401].includes(status)) {
        while (sourceQueue.length) {
          // 移除请求队列中 cancelToken-authorizationInvalid 的类型， 取消队列中的所有请求
          (<SourceType>sourceQueue.pop()).cancel("cancelToken-authorizationInvalid");
        }

        // 登录失效
        await httpInteracte["LOGIN_FAILURE"]();
      } else if ([403].includes(status)) {
        if (this.isOutLoop()) {
          httpInteracte["API_AUTH"](status);
        }
      } else if (/^[45]\d\d$/.test(status.toString())) {
        // 其它的400或者500错误的处理
        if (this.isOutLoop()) {
          httpInteracte["API_SERVER_ERROR"](status);
        }
      }

      // 其他报错情况，退出 while 循环
      return Promise.reject(error.message);
    } else {
      // console.log(error);
      return Promise.reject("未知报错");
    }
  }
}

/**
 * POST 请求类
 */
class PostHttp extends Http {
  constructor(url: any, data: any, config: any) {
    super(url, data, config);
    this.method = "post";
  }

  convertData: () => string | object = () => {
    if (this.config.headers["Content-Type"] === "formType") {
      // data序列化，对应content-type: 'application/x-www-form-urlencoded'
      return qs.stringify(this.data);
    } else {
      return this.data;
    }
  };
}

/**
 * PUT 请求类
 */
class PutHttp extends Http {
  constructor(url: any, data: any, config: any) {
    super(url, data, config);
    this.method = "put";
  }

  convertData() {
    if (this.config.ContentType === "formType") {
      // data序列化，对应content-type: 'application/x-www-form-urlencoded'
      return qs.stringify(this.data);
    } else {
      return this.data;
    }
  }
}

/**
 * GET 请求类
 */
class GetHttp extends Http {
  constructor(url: any, data: any, config: any) {
    super(url, data, config);
    this.method = "get";
  }

  convertParams: () => any = () => {
    const data: any = { ...this.data };
    // 非ie浏览器，不需要加url上加_t
    if (!(!!(window as any).ActiveXObject || "ActiveXObject" in window)) {
      return data;
    }
    // ie浏览器,url加上_t参数,防止ie浏览器get请求缓存
    if (!Object.prototype.hasOwnProperty.call(this.data, "_t")) {
      data._t = Date.now();
    } else {
      data._t_t_t_t = Date.now();
    }
    return data;
  };
}

/**
 * DELETE 请求类
 */
class DeleteHttp extends Http {
  constructor(url: any, data: any, config: any) {
    super(url, data, config);
    this.method = "delete";
  }

  convertParams() {
    return this.data;
  }
}

export default {
  async post(url: string, data: object, config: AxiosRequestConfig) {
    return new PostHttp(url, data, config).request();
  },
  put(url: string, data: object, config: AxiosRequestConfig) {
    return new PutHttp(url, data, config).request();
  },
  get(url: string, data: object, config: AxiosRequestConfig) {
    return new GetHttp(url, data, config).request();
  },
  delete(url: string, data: object, config: AxiosRequestConfig) {
    return new DeleteHttp(url, data, config).request();
  },
};

// 设置响应超时默认值
export const setDefaultTimeout = (timeStamps: number): void => {
  Http.defaultTimeout = timeStamps;
};

// 设置响应超时重试最大次数
export const setDefaultMaxRetryTimes = (times: number): void => {
  Http.maxRetryTimes = times;
};

// 设置请求成功时，接口响应数据正常的 code
export const setResponseOkCode = (code: number): void => {
  Http.RESPONSE_OK_CODE = code;
};
